﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Xml.Linq;

namespace Basic
{
    public class Tokenizer
    {
        XDocument tokenTree;
        List<string> EnabledGroups;
        public bool AutoOptimize = false;

        public Tokenizer()
        {
            LoadTokenSet(Assembly.GetExecutingAssembly().GetManifestResourceStream("Basic.Tokens.xml"));
        }

        public void LoadTokenSet(Stream TokenXml)
        {
            StreamReader reader = new StreamReader(TokenXml);
            string xml = reader.ReadToEnd();
            reader.Close();
            tokenTree = XDocument.Parse(xml);
        }

        public TokenizerOutput Tokenize(string Basic)
        {
            TokenizerOutput output = new TokenizerOutput();
            output.Errors = new List<TokenizerError>();
            List<byte> tokenizedOutput = new List<byte>();
            int LineNumber = 0;

            string[] Lines = Basic.Replace("\r", "").Split('\n');

            foreach (string _line in Lines)
            {
                string line = _line.Trim();
                while (line.Length != 0)
                {
                    // Tokenize
                    TokenMatch match = GetBestMatch(line);
                    if (match == null)
                    {
                        output.Errors.Add(new TokenizerError()
                        {
                            Message = "Invalid token",
                            Index = 0,
                            Line = LineNumber,
                        });
                        line = "";
                    }
                    else
                    {
                        byte[] tokenOut = new byte[match.Value.Length];
                        for (int i = 0; i < match.Value.Length; i += 2)
                        {
                            tokenizedOutput.Add(byte.Parse(match.Value.Substring(i, 2), System.Globalization.NumberStyles.HexNumber));
                        }
                        line = line.Substring(match.TokenString.Length);
                    }
                }
                tokenizedOutput.Add(0x3F);
                LineNumber++;
            }
            tokenizedOutput.RemoveAt(tokenizedOutput.Count - 1);

            output.Success = output.Errors.Count == 0;
            output.Output = tokenizedOutput.ToArray();
            return output;
        }

        private TokenMatch GetBestMatch(string line)
        {
            int bestLength = 0;
            XElement bestMatch = null;
            string currentByteString = "";
            TokenMatch finalMatch = null;
            foreach (XElement element in tokenTree.Root.Elements("Token"))
            {
                if (element.Attribute("string") != null)
                {
                    string token = element.Attribute("string").Value;
                    if (line.StartsWith(token))
                    {
                        if (token.Length > bestLength)
                        {
                            bestMatch = element;
                            bestLength = token.Length;
                            if (element.Attribute("byte") != null)
                                currentByteString = element.Attribute("byte").Value.Replace("$", "");
                            if (finalMatch == null)
                                finalMatch = new TokenMatch();
                            finalMatch.Value = currentByteString;
                            finalMatch.TokenString = token;
                        }
                    }
                    foreach (XElement alt in element.Elements("Alt"))
                    {
                        if (alt.Attribute("string") != null)
                        {
                            token = alt.Attribute("string").Value;
                            if (line.StartsWith(token))
                                if (token.Length > bestLength)
                                {
                                    bestMatch = element;
                                    bestLength = token.Length;
                                    if (element.Attribute("byte") != null)
                                        currentByteString = element.Attribute("byte").Value.Replace("$", "");
                                    if (finalMatch == null)
                                        finalMatch = new TokenMatch();
                                    finalMatch.Value = currentByteString;
                                    finalMatch.TokenString = token;
                                }
                        }
                    }
                }
                if (element.Elements("Token").Count() != 0) // Probably faster this way, but not strictly needed
                {
                    TokenMatch backupMatch = finalMatch;
                    finalMatch = GetBestMatch(line, element, bestLength, element.Attribute("byte").Value.Replace("$", ""), finalMatch);
                    if (finalMatch != null)
                    {
                        bestLength = finalMatch.TokenString.Length;
                        currentByteString = finalMatch.Value;
                    }
                    else
                        finalMatch = backupMatch;
                }
            }
            return finalMatch;
        }

        private TokenMatch GetBestMatch(string line, XElement Element, int bestLength, string currentByteString, TokenMatch finalMatch)
        {
            foreach (XElement element in Element.Elements("Token"))
            {
                if (element.Attribute("string") != null)
                {
                    string token = element.Attribute("string").Value;
                    if (line.StartsWith(token))
                    {
                        if (token.Length > bestLength)
                        {
                            bestLength = token.Length;
                            if (element.Attribute("byte") != null)
                                currentByteString += element.Attribute("byte").Value.Replace("$", "");
                            if (finalMatch == null)
                                finalMatch = new TokenMatch();
                            finalMatch.Value = currentByteString;
                            finalMatch.TokenString = token;
                        }
                    }
                    foreach (XElement alt in element.Elements("Alt"))
                    {
                        if (alt.Attribute("string") != null)
                        {
                            token = alt.Attribute("string").Value;
                            if (line.StartsWith(token))
                                if (token.Length > bestLength)
                                {
                                    bestLength = token.Length;
                                    if (element.Attribute("byte") != null)
                                        currentByteString += element.Attribute("byte").Value.Replace("$", "");
                                    if (finalMatch == null)
                                        finalMatch = new TokenMatch();
                                    finalMatch.Value = currentByteString;
                                    finalMatch.TokenString = token;
                                }
                        }
                    }
                }
                if (element.Elements("Token").Count() != 0) // Probably faster this way, but not strictly needed
                {
                    TokenMatch backupMatch = finalMatch;
                    finalMatch = GetBestMatch(line, element, bestLength, element.Attribute("byte").Value.Replace("$", ""), finalMatch);
                    if (finalMatch != null)
                    {
                        bestLength = finalMatch.TokenString.Length;
                        currentByteString = finalMatch.Value;
                    }
                    else
                        finalMatch = backupMatch;
                }
            }
            return finalMatch;
        }

        class TokenMatch
        {
            public string TokenString;
            public string Value;
        }

        public void EnableTokenGroup(string Group)
        {
        }
    }
}
